﻿using System;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;
using System.Globalization;

namespace XAMSDK
{
    namespace Types
    {
        internal enum XAMHandle : long { Null }
        internal enum XSystemHandle : long { Null }
        internal enum XIteratorHandle : long { Null }
        internal enum XSetHandle : long { Null }
        internal enum XStreamHandle : long { Null }
        public enum XAsyncHandle : long { Null }
        public enum XPolicyType { Access, Management, BaseRetention, AutoDelete, Shred, Storage, RetentionEnabled, RetentionDuration }
        public enum XAsyncType { XSetOpen, XSetCommit, XSetCopy, XStreamOpenInput, XStreamOpenOutput, XStreamClose, XStreamRead, XStreamWrite } 

        /// <summary>
        /// Simple class for mapping particular etypes of policy available on an object
        /// </summary>
        public class XPolicy
        {
            XString name = new XString();
            XPolicyType type;
            #region Constructors
            /// <summary>
            /// Internal constructor
            /// </summary>
            /// <param name="n">name of the policy</param>
            /// <param name="t">the XPolicyType</param>
            internal XPolicy(XString n, XPolicyType t)
            {
                name = n;
                type = t;
            }
            #endregion
            #region Methods
            /// <summary>
            /// Get a string representation of an XPolicy
            /// </summary>
            /// <returns>the string representation</returns>
            public override string ToString()
            {
                return "Policy " + name + " Type " + type;
            }
            #endregion
            #region Properties
            /// <summary>
            /// The string representaion of the XPolicy
            /// </summary>
            public XString Name { get { return name; } }

            /// <summary>
            /// The XPolicyType enum of the XPolicy
            /// </summary>
            public XPolicyType Type { get { return type; } }
            #endregion
        }

        /// <summary>
        /// A XUID object, with both byte and XString representaion and implicit casts
        /// </summary>
        public class XUID: IDisposable
        {
            private byte[] storage = new byte[Constants.XUID_SZ];
            private static byte[] sEMC_OID = { 0x00, 0x04, 0x73 };
            #region IDisposable
            protected bool disposed = false;
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            // The "disposing" boolean allows derived objects to add an additional step to dispose
            // of any IDisposable objects they own dependent on who is calling it i.e. diectly or via the dtor.
            public void Dispose(bool disposing)
            {
                if (disposing)
                {
                    // Cleanup managed objects
                }

                if (!disposed)
                {
                    // Cleanup any unmanaged objects
                    disposed = true;
                }
            }
            #endregion

            #region Constructors
            /// <summary>
            /// Simple constructor which just allocates memory for the XUID
            /// </summary>
            public XUID()
            {
            }


            /// <summary>
            /// /// Initialisation constructor using a supplied string represnting a XUID
            /// If this is not a base64 encoded string of the correct length then it is asssumed to be a legacy Content Address
            /// </summary>
            /// <param name="inXUIDString">string representing the XUID</param>
            public XUID(string inXUIDString)
            {
                int vStatus = 0;
                bool vValidXUID = false;

                if (validContentAddress(inXUIDString))
                {
                    // Legacy clip
                    storage = convert(inXUIDString);
                }
                else
                {
                    // Use standard C# as there are "issues" with the XAM one
                    try
                    {
                        storage = Convert.FromBase64String(inXUIDString);

                        if (storage.Length > Constants.XUID_SZ)
                            throw new XAMException(Errors.BAD_XUID_FORMAT, "Convert.FromBase64EncodedString - too long");
                    }
                    catch
                    {
                        throw new XAMException(Errors.BAD_XUID_FORMAT, "Convert.FromBase64EncodedString - invalid base64 string");
                    }

                    if (storage[0] != 0
                        || storage[1] != 0 || storage[2] != 4 || storage[3] != 115 // EMC OID
                        || storage[4] != 0
                        || storage[5] > 80 // MAx length of XUID = 80 (8 bits reserved)
                        || storage[6] != 0
                        || storage[7] != 0)
                    {
                        throw new XAMException(Errors.BAD_XUID_FORMAT, "Invalid data in bytes 0-7");
                    }
                    else
                    {
                        vStatus = Imports.XAMToolkit_IsValidXUID(storage, out vValidXUID);
                        Helpers.CheckAndThrow(vStatus, "XAMToolkit_IsValidXUID");
                    }
                }
            }

            /// <summary>
            /// Initialisation constructor using a supplied byte[]
            /// </summary>
            /// <param name="inXUID">the raw bytes value of the XUID</param>
            public XUID(byte[] inXUID)
            {
                if (Constants.XUID_SZ < inXUID.Length)
                {
                    XAMException e = new XAMException(1029, "Invalid XUID Length");
                }
                Array.Copy(inXUID, storage, inXUID.Length);
            }
            #endregion

            #region Implicit Casts

            /// <summary>
            /// Implicit cast for XUID to byte[]
            /// </summary>
            /// <param name="x">a XUID instance</param>
            /// <returns>the raw storage</returns>
            static public implicit operator byte[](XUID x)
            {
                return x.storage;
            }

            /// <summary>
            /// Implicit cast fro XUID to String
            /// </summary>
            /// <param name="x">a XUID instance</param>
            /// <returns>the string representation of the XUID</returns>
            static public implicit operator String(XUID x)
            {
                XString xuidString = new XString();

                if (x.storage.Length == Constants.XUID_SZ)
                {
                    int retval = Imports.XAMToolkit_XUIDtoString(x, xuidString);
                }
                else
                {
                    xuidString = Helpers.encoder.GetString(x);
                }

                return xuidString;
            }
            #endregion

            #region Properties

            /// <summary>
            /// Does the initialised value represent a valid XUID?
            /// Note: this does not mean thatit repereents a physical XSet, merely that it fits the "rules" for a XUID
            /// </summary>
            public bool IsValid
            {
                get
                {
                    bool valid = false;
                    int retval = Imports.XAMToolkit_IsValidXUID(storage, out valid);
                    Helpers.CheckAndThrow(retval, "XAMToolkit_IsValidXUID");
                    return valid;
                }
            }

            public String ContentAddress
            {
                get
                {
                    byte[] ca = new byte[80];
                    Array.Copy(storage, 8, ca, 0, 53);

                    return Helpers.encoder.GetString(ca);
                }
            }
            #endregion

            #region Methods
            public byte[] convert(string inCA)
            {
                byte[] bXUID = System.Text.Encoding.UTF8.GetBytes(inCA);
                byte[] vTempXUID = new byte[Constants.XUID_SZ];

                int vCALength = inCA.Length;
                int vXUIDLength = vCALength + 8; // 8 reserved bits        	

                vTempXUID[0] = (byte)'\0'; //reserved by SNIA
                Array.Copy(sEMC_OID, 0, vTempXUID, 1, sEMC_OID.Length);
                vTempXUID[4] = (byte)'\0'; //reserved by SNIA
                vTempXUID[5] = (byte)vXUIDLength;
                vTempXUID[6] = (byte)'\0';
                vTempXUID[7] = (byte)'\0';
                Array.Copy(bXUID, 0, vTempXUID, 8, vCALength);

                int vCalcCRC = CRC16.Instance.compute(vTempXUID, vXUIDLength);
                String crcString = vCalcCRC.ToString("X4");

                vTempXUID[6] = (byte)int.Parse(crcString.Substring(0, 2), NumberStyles.AllowHexSpecifier);
                vTempXUID[7] = (byte)int.Parse(crcString.Substring(2, 2), NumberStyles.AllowHexSpecifier);

                // get the actual Length of the XUID
                Array.Copy(bXUID, 0, vTempXUID, 8, bXUID.Length);

                // pad the mXUID with '\0'
                for (int i = vXUIDLength; i < 80; i++)
                    vTempXUID[i] = (byte)'\0';

                return vTempXUID; ;
            }

            public static bool validContentAddress(string clipID)
            {
                if (clipID[13] != 'e')
                    return false;

                if (clipID.Length != 27 && clipID.Length != 53)
                    return false;

                // OK - it looks right, but lets make sure that all the characters are actuallly
                // valid for a Cenetera Content Address i.e. 0-9, A-V
                for (int i = 0; i < clipID.Length; i++)
                {
                    if (i == 13)
                        continue;

                    if (clipID[i] < '0' || clipID[i] > 'V')
                        return false;

                    if (clipID[i] > '9' && clipID[i] < 'A')
                        return false;
                }

                // We're as sure as we can be that this is a content address
                return true;
            }

            #endregion

            internal class CRC16
            {
                static ushort[] table = new ushort[256];
                const ushort polynomial = 0xA001;
                static CRC16 instance = null;

                public ushort compute(byte[] bytes, int length)
                {
                    ushort crc = 0;

                    for (int i = 0; i < length; i++)
                    {
                        byte index = (byte)(crc ^ bytes[i]);
                        crc = (ushort)((crc >> 8) ^ table[index]);
                    }

                    return crc;
                }

                static public CRC16 Instance
                {
                    get
                    {
                        if (instance == null)
                            instance = new CRC16();

                        return instance;
                    }
                }

                private CRC16()
                {
                    ushort value;
                    ushort temp;

                    for (ushort i = 0; i < table.Length; i++)
                    {
                        value = 0;
                        temp = i;

                        for (byte j = 0; j < 8; j++)
                        {
                            if (((value ^ temp) & 0x0001) != 0)
                                value = (ushort)((value >> 1) ^ polynomial);
                            else
                                value >>= 1;

                            temp >>= 1;
                        }

                        table[i] = value;
                    }
                }
            }
        }

        /// <summary>
        /// Fixed size string  which models the xam_string type using a standard .NET StringBuilder for storage
        /// </summary>
        public class XString: IDisposable
        {
            private StringBuilder sb;

            #region IDisposable
            protected bool disposed = false;
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            // The "disposing" boolean allows derived objects to add an additional step to dispose
            // of any IDisposable objects they own dependent on who is calling it i.e. diectly or via the dtor.
            public void Dispose(bool disposing)
            {
                if (disposing)
                {
                    // Cleanup managed objects
                }

                if (!disposed)
                {
                    // Cleanup any unmanaged objects
                    //sb = null;
                    disposed = true;
                }
            }
            #endregion
            #region Constructors

            /// <summary>
            /// Standard constructor to create an XString from a string
            /// </summary>
            /// <param name="inString"></param>
            public XString(string inString)
            {
                sb = new StringBuilder(inString, Constants.STRING_SZ);
            }

            /// <summary>
            /// Simple constructor which simply allocates memory
            /// </summary>
            public XString()
            {
                sb = new StringBuilder(Constants.STRING_SZ);
            }
            #endregion

            #region Implicit Casts

            /// <summary>
            /// Implicit cast for String to XString
            /// </summary>
            /// <param name="s">string to be cast</param>
            /// <returns>an XString instance</returns>
            static public implicit operator XString(String s)
            {
                return new XString(s);
            }

            /// <summary>
            /// Implicit cast for XString to StringBuilder
            /// </summary>
            /// <param name="s">XString to be cast</param>
            /// <returns>the StringBuilder storage of the XString</returns>
            static public implicit operator StringBuilder(XString s)
            {
                return s.sb;
            }

            /// <summary>
            /// Implicit cast for XString to String
            /// </summary>
            /// <param name="s">an XString instance</param>
            /// <returns>string representation of the XString</returns>
            static public implicit operator String(XString s)
            {
                return s.ToString();
            }

            #endregion

            #region Methods
            /// <summary>
            /// Get the string representation of the XString
            /// </summary>
            /// <returns>string representation</returns>
            public override string ToString()
            {
                return sb.ToString();
            }
            #endregion
        }

        /// <summary>
        /// Definition of the callback method signature for Async operations.
        /// Specifically annotated as an UnmanagedFunctionPointer using the Cdecl calling convention
        /// </summary>
        /// <param name="h">the XAsyncHandle of the operation</param>
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void XAsyncCallback(XAsyncHandle h);
    }
}